package Agents;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import ec.util.MersenneTwisterFast;

import Model.SimModel;
import Param.Constants;
import Helpers.Common;

import sim.engine.SimState;
import sim.engine.Steppable;
import sim.engine.Stoppable;
import sim.portrayal.simple.OvalPortrayal2D;
import sim.portrayal.simple.RectanglePortrayal2D;
import sim.util.Bag;
import sim.util.Int2D;

@SuppressWarnings("serial")
public abstract class Entity extends OvalPortrayal2D implements Steppable {
	/**
	 * Lien sur le modèle
	 */
	protected SimModel model;
	protected Stoppable stoppable;

	/**
	 * Caracteristiques dynamiques
	 */
	protected Int2D position;
	protected int life;
	
	/**
	 * Carré sous-jacent pour "attack"
	 */
	protected RectanglePortrayal2D rectAttack = new RectanglePortrayal2D(new Color(233, 44, 44, 255));

	public Entity(SimModel m, Int2D position){
		model = m;
		this.position = position;
	}

	public Int2D getPos(){
		return position;
	}

	protected void displayRectAttack()
	{
		rectAttack.filled = false;
		this.model.yard.setObjectLocation(rectAttack, this.getPos());
	}
	
	protected Bag detectAdjacent() {
		Bag adjacent = new Bag();
		model.yard.getNeighborsMaxDistance(position.x,position.y, 1, false, adjacent, null, null);
		adjacent.remove(this);
		return adjacent;
	}

	protected Bag detectInPerception() {
		Bag detect = new Bag();
		int perception = this instanceof Zombie ? Constants.ZOMBIE_PERCEPTION : Constants.HUMAN_PERCEPTION;
		model.yard.getNeighborsMaxDistance(position.x,position.y, perception, false, detect, null, null);
		detect.remove(this);
		return detect;
	}

	protected boolean attack(Entity e) {
		float accuracy = this instanceof Zombie ? Constants.ZOMBIE_ACCURACY : Constants.HUMAN_ACCURACY;
		int impactDamage = this instanceof Zombie ? Constants.ZOMBIE_IMPACTDAMAGE : Constants.HUMAN_IMPACTDAMAGE;
		if(new MersenneTwisterFast().nextFloat() <= accuracy) {
			e.takeDamage(impactDamage);
			return true;
		}
		return false;
	}

	public void takeDamage(int damage) {
		life -= damage;
		if(life < 0) {
			life = 0;
		}
	}



	/**
	 * Methodes utilitaires
	 */

	/**
	 * getAllowedMovement
	 * retourne la destination autorisée la plus proche d'une cible entrée en paramètre
	 * @param newPosition
	 * @return Movement
	 */
	protected Movement getAllowedMovement(Int2D newPosition) {
		int moveSpeed = this instanceof Zombie ? Constants.ZOMBIE_MOVESPEED : Constants.HUMAN_MOVESPEED;
		
		//--correction, si la position est en dehors de la grille--
		int x = newPosition.x, y = newPosition.y;
		if(x >= model.yard.getWidth())
			x = model.yard.getWidth()-1;
		else if(x < 0)
			x = 0;
		if(y >= model.yard.getHeight())
			y = model.yard.getHeight()-1;
		else if(y < 0)
			y = 0;
		newPosition = new Int2D(x,y);
		//---------------fin correction----------------------------

		//-------si la cible est trop éloignée---------------------
		Int2D tmpPosition = newPosition;//destination accessible la plus proche de la cible
		Movement allowedMovement = new Movement(new Int2D(-1, -1), Common.distance(position,tmpPosition), 0); //destination effective et distance entre cette distination et la destination voulue
		if(allowedMovement.distStart > moveSpeed){
			if(position.x + moveSpeed < newPosition.x)
				x = position.x + moveSpeed;
			else if(newPosition.x < position.x - moveSpeed)
				x = position.x - moveSpeed;
			else
				x = newPosition.x;
			if(position.y + moveSpeed < newPosition.y)
				y = position.y + moveSpeed;
			else if(newPosition.y < position.y - moveSpeed)
				y = position.y - moveSpeed;
			else
				y = newPosition.y;
			tmpPosition = new Int2D(x,y);
			allowedMovement.distStart = moveSpeed;
		}
		allowedMovement.distTarget = Common.distance(tmpPosition,newPosition);
		allowedMovement.distStart = Common.distance(position,tmpPosition);
		//---------------------------------------------------------

		//-----------si la destination est occupée-----------------
		int remainingDistance = 0;	//distance entre la destination effective et la destination voulue
		if(model.yard.getObjectsAtLocation(tmpPosition) != null){//si la case de destination est occupée
			//System.out.println(tmpPosition + " occupee");
			List<Int2D> listPosition = new ArrayList<Int2D>(); //liste de toutes les cases adjacentes (destinations temporaires)
			int tmpDistance = 0;//distance entre le depart et la destination temporaire
			while(allowedMovement.position.x==-1 && remainingDistance < moveSpeed){
				listPosition.clear();
				remainingDistance++;
				for(int i=-remainingDistance;i<=remainingDistance;i++){ //construction de mla liste des cases adjacentes
					for(int j=-remainingDistance;j<=remainingDistance;j++){
						if(Math.max(Math.abs(i),Math.abs(j)) == remainingDistance){
							if(tmpPosition.x+i < model.yard.getWidth() && tmpPosition.x+i >= 0 && tmpPosition.y+j < model.yard.getHeight() && tmpPosition.y+j >= 0){
								listPosition.add(new Int2D(tmpPosition.x + i, tmpPosition.y + j));
							}
						}
					}
				}
				for(int i=0; i<listPosition.size();i++){//pour chaque case adjacente
					if(model.yard.getObjectsAtLocation(listPosition.get(i)) == null){ //Case libre
						tmpDistance = Common.distance(position, listPosition.get(i)); //distance entre le d�part et la destination (adjcente)
						
						//System.out.println(position + "->" + listPosition.get(i)+ "=" +tmpDistance + "<" + moveSpeed);
						if(tmpDistance<=moveSpeed && (allowedMovement.position.x == -1 || tmpDistance<allowedMovement.distStart)){ //Si meilleur position que celle actuelle
							allowedMovement.position = listPosition.get(i);
							allowedMovement.distStart = tmpDistance;
						}
					}
					else if(position == listPosition.get(i)){
						allowedMovement.position = position;
						allowedMovement.distStart = 0;
					}
				}
			}
			allowedMovement.distTarget = Common.distance(newPosition, allowedMovement.position);
		}
		//-------------si la case est libre------------------------------
		else allowedMovement.position = tmpPosition;

		//System.out.println("Allowed Mov" + allowedMovement.position);
		return allowedMovement;
	}

	protected void move(Int2D newPosition) {
		//if(this.position.x != newPosition.x && this.position.y != newPosition.y){
		model.yard.remove(this);
		this.position = newPosition;
		model.yard.setObjectLocation(this, newPosition);
		//}
	}

	protected class Movement{
		public Int2D position;
		public int distTarget;
		public int distStart;

		public Movement(Int2D pos, int disS, int disT){
			position = pos;
			distTarget = disT;
			distStart = disS;
		}
	}

	public void setStoppable(Stoppable s){
		this.stoppable = s;
	}

	public int getLife(){
		return this.life;
	}

	public void survive() {
		if(life <= 0) {
			//System.out.println(this.getClass().toString() + " -> Mort");
			die();
		}
	}

	protected abstract void die();
	public abstract void step(SimState state);
}
